package parallel.project.graph;

import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * Defines a node in a {@link Graph}.  A node is identified by a name
 * which must be unique in a {@link Graph}.  A node can also contain a
 * maze 2D coordinate location.
 * 
 * @author Adam Clayton
 * @author Larry Walters
 */
public class Node implements Serializable
{
	/** Name of the node */
	private String name;
	
	/** The {@link Edge}s associated with this node */
	private List<Edge> edges;
	
	/** The location on a maze grid of this node */
	private Point location;

	/**
	 * Create a new {@link Node} with no edges, at the given location,
	 * where the name is a string representation of the location.
	 * 
	 * @param loc
	 * 		Location of the node on the maze grid
	 */
	public Node(Point loc)
	{
		this.location = loc;
		this.name = "(" + location.x + "," + location.y + ")";
		this.edges = new ArrayList<Edge>();	
	}
	
	/**
	 * Create a new {@link Node} with the given name and no edges, 
	 * at the given location
	 * 
	 * @param name
	 * 		Name of the node
	 * @param loc
	 * 		Location of the node on the maze grid
	 */
	public Node(String name, Point loc)
	{
		this.name = name;
		this.edges = new ArrayList<Edge>();
		this.location = loc;
	}
	
	/**
	 * Associates the given {@link Edge} with this {@link Node}
	 * 
	 * @param edge
	 * 		The {@link Edge} to add
	 * @throws GraphException
	 */
	public void addEdge(Edge edge) throws GraphException
	{
		// if the edge is not already associated with this node, add it
		if (!this.edges.contains(edge))
			this.edges.add(edge);
		// otherwise, throw exception
		else
		{
			throw new GraphException("Node " + this.getName() + 
				" already has edge " + edge.toString());
		}
	}
	
	@Override
	public boolean equals(Object o)
	{
		if (!(o instanceof Node))
			return false;
		Node other = (Node)o;
		// check that all parts are the same
		if (!this.name.equals(other.name) ||
			!this.edges.equals(other.edges))
			return false;
		if (this.location != null)
		{
			if (!this.location.equals(other.location))
				return false;
		}
		else
		{
			if (other.location != null)
				return false;
		}
		return true;
	}
	
	/**
	 * Returns the number of edges going in and coming out of this
	 * node.
	 * 
	 * @return
	 * 		Number of edges associated with this node
	 */
	public int getEdgeCount()
	{
		return this.edges.size();
	}
	
	/**
	 * Returns all the {@link Edge}s connected to the node
	 * 
	 * @return
	 * 		All the {@link Edge}s connected to the node
	 */
	public List<Edge> getEdges()
	{
		return this.edges;
	}
	
	/**
	 * Returns the name of this {@link Node}.
	 * 
	 * @return
	 * 		Name of this {@link Node}
	 */
	public String getName()
	{
		return this.name;
	}
	
	/**
	 * Returns the location of this {@link Node} on the maze grid
	 * 
	 * @return
	 * 		Coordinate location of this {@link Node}
	 */
	public Point getLocation()
	{
		return this.location;
	}
	
	/**
	 * Removes the given {@link Edge} from the node if it is associated
	 * with it.  Note that the other node has no knowledge of this 
	 * disconnection, so it will think it is still connected to this
	 * node.
	 * 
	 * @param edge
	 * 		{@link Edge} to remove
	 * 
	 * @return
	 * 		True if the element was removed, false if not
	 */
	public boolean removeEdge(Edge edge)
	{
		return this.edges.remove(edge);
	}
	
	/**
	 * Removes the given {@link Edge}s from the node if they are 
	 * associated with it.
	 * 
	 * @param edgeList
	 * 		{@link Edge}s to remove
	 * 
	 * @return
	 * 		True if the elements were removed, false if not
	 */
	public boolean removeEdges(List<Edge> edgeList)
	{
		return this.edges.removeAll(edgeList);
	}
	
	@Override
	public String toString()
	{
		String ret = "name = " + this.name + "\n";
		ret += "edges = {";
		for (int i = 0; i < this.edges.size() - 1; i++)
			ret += this.edges.get(i).toString() + ",";
		if (this.edges.size() > 0)
			ret += this.edges.get(this.edges.size() - 1).toString();
		ret += "}\n";
		if (this.location != null)
			ret += "location = " + this.location.toString() + "\n";
		return ret;
	}
	
	/**
	 * Returns a set of all the nodes that are connected to this one
	 * via {@link Edge}s.
	 * 
	 * @return
	 * 		All nodes connected to this one
	 */
	public HashSet<Node> getNeighbors(){
		HashSet<Node> nieghbors = new HashSet<Node>();
		
		for(Edge e : getEdges()){
			if(e.getFirst() == this)
				nieghbors.add(e.getSecond());
			else
				nieghbors.add(e.getFirst());
		}
		
		return nieghbors;
	}
}
